use crate::sm3::error::Error;

pub struct Compressor<'a>{
    words: [u32;68],//W
    words_a: [u32;64],//W'
    padded_buffer:&'a [u8]

}


impl<'a> Compressor<'a> {
    pub fn new(padded_buffer:&'a [u8])->Self {
        

        #[cfg(feature="print_compressor")]
        println!("[Compressor]:padded_buffer:{:x?}",padded_buffer);

        Self{
            words:[0;68],
            words_a:[0;64],
            padded_buffer:padded_buffer
        }
    }

    pub fn hash(&mut self,v:&mut [u32],t:&[u32])->Result<(),Error>{
        

        self.set_words_0_15().unwrap();
        self.set_words_16_67().unwrap();
        self.set_words_apostrophe().unwrap();
        self.compress(v,t).unwrap();



        Ok(())
    }

    fn compress(&self,v:&mut [u32],t:&[u32])->Result<(),Error>{
        


        let mut a = v[0];
        let mut b = v[1];
        let mut c = v[2];
        let mut d = v[3];
        let mut e = v[4];
        let mut f = v[5];
        let mut g = v[6];
        let mut h = v[7];
    
    
        for i in 0..64usize {
            let ss1:u32 = (((a.rotate_left(12) as u64)+(e as u64)+(t[i].rotate_left(i as u32) as u64)) as u32).rotate_left(7);
            let ss2:u32 = ss1 ^ a.rotate_left(12);
            let tt1:u32 =((self.get_ff(i,a,b,c).unwrap() as u64)+(d as u64)+(ss2 as u64)+(self.words_a[i] as u64)) as u32;
            let tt2:u32 =((self.get_gg(i,e,f,g).unwrap() as u64)+(h as u64)+(ss1 as u64)+(self.words[i] as u64)) as u32;
            d = c;
            c = b.rotate_left(9);
            b = a;
            a = tt1 as u32;
            h = g;
            g = f.rotate_left(19);
            f = e;
            e = self.get_p0(tt2 as u32);
            
            #[cfg(feature="print_compressor")]
            println!("[Compressor]compressing:i:{}|a:{:x},b:{:x},c:{:x},d:{:x},e:{:x},f:{:x},g:{:x},h:{:x}",i,a,b,c,d,e,f,g,h);
        }
    
    
        v[0] ^= a;
        v[1] ^= b;
        v[2] ^= c;
        v[3] ^= d;
        v[4] ^= e;
        v[5] ^= f;
        v[6] ^= g;
        v[7] ^= h;
        #[cfg(feature="print_compressor")]
        println!("[Compressor]v:{:x?}",v);






        Ok(())
    }
    
    
    fn get_ff(&self,i:usize,x:u32,y:u32,z:u32)->Result<u32,Error>{
        if i <=15 {
            Ok(x^y^z)
        } else if i >= 16 && i<=63 {
            Ok((x & y) | (x & z) | (y & z))
        } else {
            Err(Error::new(String::from("Invalid i for get_ff !i:")))
        }
    }
    
    fn get_gg(&self,i:usize,x:u32,y:u32,z:u32)->Result<u32,Error>{
        if i <=15 {
            Ok(x^y^z)
        } else if i >= 16 && i<=63 {
            Ok((x & y) | ((!x) & z))
        } else {
            Err(Error::new(String::from("Invalid i for get_gg !i:")))
        }
    
    }
    
    
    fn get_p0(&self,x:u32)->u32{
        x ^ x.rotate_left(9) ^ x.rotate_left(17)
    }

    //P1(X) = X ⊕ (X <<< 15) ⊕ (X <<< 23)
    fn get_p1(&self,input:u32)->u32{
        input ^ input.rotate_left(15) ^ input.rotate_left(23)
    }



    fn set_words_0_15(&mut self)->Result<(),Error>{


        if self.padded_buffer.len() != 64 {
            return Err(Error::new(String::from("The size of buffer is not 64 bytes(512 bit) for words splitting!")));
        }
        if self.words.len() != 68 {
            return Err(Error::new(String::from("The size of words is not 68 for initialize words splitting!")));
        }
        for i in 0..(64/4) {
            self.words[i] = self.convert_to_u32_be(&self.padded_buffer[i*4..(i*4+4)]).unwrap();
        }

        #[cfg(feature="print_compressor")]
        println!("[Compressor]:set_words_0_15:{:x?}",self.words);



        
        Ok(())
    
    }
 
    #[allow(dead_code)]
    fn test_set_words(&mut self,testwords:&[u32;68])->Result<(),Error>{
        for i in 0..testwords.len() {
            self.words[i] =testwords[i]; 
        }
        Ok(())
    
    }
   
    
    
    fn convert_to_u32_be(&self,b:&[u8])->Result<u32,Error>{
        if b.len() != 4 {
            return Err(Error::new(String::from("The size of buffer is not 4 for convert u32!")));
        }
        let mut result = 0u32;
        result |=( b[0] as u32)<<24;
        result |=( b[1] as u32)<<16;
        result |=( b[2] as u32)<<8;
        result |=( b[3]) as u32;
        Ok(result)
    }
    
    
    
    fn set_words_16_67(&mut self)->Result<(),Error>{


        if self.words.len() != 68 {
            return Err(Error::new(String::from("The size of words is not 68 (0~15 & 16~67) !")));
        }
    
        for i in 16..=67 {
            //Wj P1(Wj−16 ⊕ Wj−9 ⊕ (Wj−3 <<< 15)) ⊕ (Wj−13 <<< 7) ⊕ Wj−6
            self.words[i] = self.get_p1(self.words[i-16] ^ self.words[i-9] ^ (self.words[i-3].rotate_left(15) )) ^( self.words[i-13].rotate_left(7)) ^ self.words[i-6];
        }

        #[cfg(feature="print_compressor")]
        println!("[Compressor]:set_words_16_67:{:x?}",self.words);
 

       

        Ok(())
    }
    
    
    fn set_words_apostrophe(&mut self)->Result<(),Error>{


        if self.words.len() != 68 {
            return Err(Error::new(String::from("The size of words is not 68 (0~15 & 16~67) for generate wors_apostrophe !")));
        }
        if self.words_a.len() != 64 {
            return Err(Error::new(String::from("The size of words_a is not 64!")));
        }
    
        for i in 0..64{
            self.words_a[i] = self.words[i] ^ self.words[i+4];
        }

        #[cfg(feature="print_compressor")]
        println!("[Compressor]:set_words_apostrophe:{:x?}",self.words_a);
  

        

        Ok(())
    }





}


#[cfg(test)]
mod tests{

    use super::*;

    #[test]
    fn test_convert_to_u32_be(){
        let test = [0x1,0x2,0x3,0x4];
        let compressor = Compressor::new(&test);
        let rtn = compressor.convert_to_u32_be(&test).unwrap();
        assert_eq!(0x0102_0304,rtn);
    }


    #[test]
    fn test_convert_to_u32_be_with_zero(){
        let test = [0x0,0x0,0x0,0x0];
        let compressor = Compressor::new(&test);
        let rtn = compressor.convert_to_u32_be(&test).unwrap();
        assert_eq!(0x0,rtn);
    }


    #[test]
    fn test_set_words_0_15(){
        let test = [    
                        0x61,0x62,0x63,0x80,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x00,
                        0x00,0x00,0x00,0x18
                    ];
        let mut compressor = Compressor::new(&test);
        compressor.set_words_0_15().unwrap();
        for i in 0..68{
            if i==0{
                assert_eq!(0x61626380,compressor.words[i],"test_set_words_0_15 error @ {}",i);
                continue;
            }
            if i==15{
                assert_eq!(0x18,compressor.words[i],"test_set_words_0_15 error @ {}",i);
                continue;
            }
            assert_eq!(0x0,compressor.words[i],"test_set_words_0_15 error @ {}",i);
        }
    }


    #[test]
    fn test_get_p1(){
        let nouse = [0x0u8];
        let compressor = Compressor::new(&nouse);
        let test = 50u32;
        assert_eq!(50u32 ^ 50u32.rotate_left(15) ^ 50u32.rotate_left(23),compressor.get_p1(test));
    }


    #[test]
    fn test_get_words_16_67(){
        let nouse:[u8;64] = [0;64];
        let mut compressor = Compressor::new(&nouse);

        let mut test:[u32;68] = [0;68];
        for i in 0..16 {
            test[i] = i as u32;
        }
        compressor.test_set_words(&test).unwrap();
        compressor.set_words_16_67().unwrap();
        for i in 16..=67 {
            let r = compressor.get_p1(compressor.words[i-16] ^ compressor.words[i-9] ^ (compressor.words[i-3].rotate_left(15) )) ^( compressor.words[i-13].rotate_left(7)) ^ compressor.words[i-6];
            assert_eq!(r,compressor.words[i]);
        }
    }


    #[test]
    fn test_set_words_apostrophe(){
        let nouse:[u8;64] = [0;64];
        let mut compressor = Compressor::new(&nouse);
        let mut test:[u32;68] = [0;68];
        for i in 0..68 {
            test[i] = i as u32;
        }
        compressor.test_set_words(&test).unwrap();
        compressor.set_words_apostrophe().unwrap();
        for i in 0..64{
            assert_eq!(compressor.words[i] ^ compressor.words[i+4],compressor.words_a[i]);
        }
    }




}
